/*
This is a sample program to implement a server, which should listen
on a port, wait for incoming connections, and send the content
of a requested file. It handles multiple connections at the same time
using POSIX threads.

Usage: server <port>

Author: Akos Fabian
Date: 2012.05.03

*/

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <error.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>

#define MAX_CLIENTS     5
#define MSG_LEN         32768
#define STR_LEN         256

int port = 0;	            // listening port
int sockfd = 0;	            // listening socket
pthread_t threads[MAX_CLIENTS];	//array for the threads
int actual_thread = 0;
pthread_mutex_t mutex;      // protecting the threads array

int read_arguments(int, char**);
int prepare_socket();
int start_listening();
int handle_client();
void cleanup();
void signal_callback_handler(int);
int register_task(pthread_t thread);
int set_finished(pthread_t);
void wait_for_threads();

// Thread function to handle client request
// argument: the communication socket to use
void* thread_func(void* socket)
{	
    // STDOUT is not protected from concurrent access,
    // not important
	printf("Request handled in a new thread (0x%x)\n", (unsigned int)pthread_self());
	int commfd = *((int*)socket);
	
	// Read filename from client
    char filename[STR_LEN];
    memset(filename, 0, STR_LEN); // zero out the buffer
    int n = recv(commfd, filename, STR_LEN - 1, 0);
    if(n < 0)
    {
        printf("Error reading from socket (%s)\n", strerror(errno));
        set_finished(pthread_self());   // Register that thread has exited
        pthread_exit(NULL);
    }
    
    // Send the requested file
    // File is only read, no need to protect from
    // concurrent access
    FILE* fp = fopen(filename, "r");    // open for reading
    if(!fp)
    {
        printf("Cannot open file (%s)\n", strerror(errno));
        set_finished(pthread_self());   // Register that thread has exited
        pthread_exit(NULL);
    }
    char buffer[MSG_LEN];
    int read_count = 0;
    int sent_count = 0;
    do
    {
        read_count = fread(buffer, 1, MSG_LEN, fp);
        sent_count = send(commfd, buffer, read_count, 0);
        if(sent_count < 0)
        {
            printf("Error writing to socket (%s)\n", strerror(errno));
            set_finished(pthread_self());   // Register that thread has exited
            pthread_exit(NULL);
        }
        // Make it slow to test multi-client handling
        sleep(1);
        printf(".");
        fflush(NULL);
    }while (read_count > 0);
    printf("\n");
    
    fclose(fp);
    close(commfd);
    
    set_finished(pthread_self());   // Register that thread has exited
    pthread_exit(NULL);
}

// Main function
// Expected argument: the port number to listen on
int main(int argc, char** argv)
{
    int res = 0;
    
    memset(threads, 0, MAX_CLIENTS * sizeof(pthread_t)); // clear array
    pthread_mutex_init(&mutex, NULL);        // initialize mutex
    //pthread_attr_init(mattr);               // initialize mutex attribute

    printf("Starting server...\n");
    
    // Register exit signal handler
    signal(SIGINT, signal_callback_handler);
    
    // Process command line parameters
    res = read_arguments(argc, argv);
    if(res)
    {
        exit(res);
    }

    res = prepare_socket();
    if(res)
    {
        exit(res);
    }

    res = start_listening();
    if(res)
    {
        exit(res);
    }
    
    // Wait endlessly to client requests
    // Terminate with signals!
    while(1)
    {
        res = handle_client();
        if(res)
        {
            exit(res);
        }
    }

    cleanup();    

    return 0;
}

// Read port number
int read_arguments(int argc, char** argv)
{
    // Make sure that port number is provided as a parameter
    if(argc != 2)
    {
        printf("Port number not provided!\n");
        printf("Usage: %s <port>\n", argv[0]);
        return(1);
    }

    // Parse port number from 2nd command line argument
    port = atoi(argv[1]);   // TODO: must be <65536
    if(port == 0)
    {
        printf("Given port is not a number! (%s)\n", argv[1]);
        return(1);
    }

    printf("Port number: %d\n", port);
    return 0;
}

// Create socket and set options
int prepare_socket()
{
    struct sockaddr_in server_addr;     // server adsress
    socklen_t srvlen;
    
    // Create a TCP socket
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0)
    {
        printf("Error opening socket (%s)\n", strerror(errno));
        return(1);
    }

    // Enable address reuse
    int option = 1;         // socket option
    int rc = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option));
    if(rc)
    {
        printf("Error setting socket options! (%s)\n", strerror(errno));
        return(1);
    }

    // Set up server address properly ("man 7 ip")
    memset((char*)&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port);         // in network byte order
    server_addr.sin_addr.s_addr = INADDR_ANY;   // from any interface

    srvlen = sizeof(server_addr);
    // Bind the socket to the given port
    if(bind(sockfd, (struct sockaddr*)&server_addr, srvlen) < 0)
    {
        printf("Error binding socket! (%s)\n", strerror(errno));
        return(1);
    }
    
    return 0;
}

// Start to listen on socket
// MAX client maximizes the queue size and the
// parallel handling tasks also
int start_listening()
{
    // Listen on the socket
    if(listen(sockfd, MAX_CLIENTS))
    {   
        printf("Error listening on socket! (%s)\n", strerror(errno));
        return(1);
    }
    printf("Waiting for requests...\n");
     
    return 0;
}

// Handle an incoming request
// Contains a blocking call to "accept"
int handle_client()
{
    printf("Handling client request...\n");
    struct sockaddr_in cli_addr;    // client address
    socklen_t clilen;	            // client address lengths
    clilen = sizeof(cli_addr);
    
    int commfd;     // the communication socket
    // Accept connections
    // Blocking call
    commfd = accept(sockfd, (struct sockaddr *)&cli_addr, &clilen);
    if(commfd < 0)
    {
        printf("Error on accept (%s)\n", strerror(errno));
        return(1);
    }
    
    // Make threads joinable
    pthread_attr_t attr;
   	pthread_attr_init(&attr);
   	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

    pthread_t thread = 0;   // new thread id

    // Start a new thread for the job
    int result = pthread_create(&thread, &attr, thread_func, (void *)&commfd);
	if (result)
    {
 		printf("Error starting thread (%d)\n", result);
 		exit(-1);
	}
	printf("Thread created 0x%x\n", (unsigned int)thread);
	int reg = register_task(thread);
    if (reg)
    {
 		printf("Error registering task (%d)\n", reg);
	}
	
    return 0;
}

int register_task(pthread_t thread)
{
    // protect access to global array "threads"
    pthread_mutex_lock(&mutex);
    
    // Find a free place in the array
    int i = 0;
    char done = 0;
    for(i=0; i<MAX_CLIENTS; i++)
    {
        if(threads[i] == 0)
        {
            threads[i] = thread;
            done = 1;
            break;
        }
    }
    
    pthread_mutex_unlock(&mutex);
    
    if(!done)
    {
        printf("Threads exeed maximum number (%d)\n", MAX_CLIENTS);
        return 1;
    }
    else
    {
        printf("Task registered successfully\n");
        return 0;
    }
}

int set_finished(pthread_t thread)
{
    // protect access to global array "threads"
    pthread_mutex_lock(&mutex);
    
    // Find thread in the array
    int i = 0;
    char done = 0;
    for(i=0; i<MAX_CLIENTS; i++)
    {
        if(threads[i] == thread)
        {
            threads[i] = 0;
            done = 1;
            break;
        }
    }
    
    pthread_mutex_unlock(&mutex);
    
    if(!done)
    {
        printf("Thread not found (0x%x)\n", (unsigned int)thread);
        return 1;
    }
    else
    {
        printf("Thread de-registered successfully\n");
        return 0;
    }
}

// Wait for threads to finish, do some cleanup
void cleanup()
{
    wait_for_threads();  // Wait for threads to finish
    
    if(sockfd)
    {
        close(sockfd);
    }
    pthread_mutex_destroy(&mutex);
    printf("Server is done.\n");
}

// Wait for threads to finish
void wait_for_threads()
{
    printf("Waiting for threads to finnish...\n");

    pthread_mutex_lock(&mutex);
    
    int i;
    for(i=0; i<MAX_CLIENTS; i++)
    {
        if(threads[i])
        {
            pthread_join(threads[i], NULL);
        }
    }
    
    pthread_mutex_unlock(&mutex);
}

// The function to be called when ctrl-c (SIGINT) 
// signal is sent to process
void signal_callback_handler(int signum)
{
   printf("Caught signal %d\n",signum);
   cleanup();
 
   // Terminate program
   exit(0);     // Correct exit status
}


